﻿using System;
using System.Collections.Generic;
using System.Linq;
using Sustainalytics.ESGRatings.Primitives;

namespace Sustainalytics.ESGRatings.BusinessLogic.Computing.Tests
{
    [Serializable]
    public class ESGData
    {
        public int companiesCount = 0; //641 * 8
        public int indicatorsCount = 150;
        public int[] groupsHeads;

        [NonSerialized]
        bool initialized = false;
        
        [NonSerialized]
        IESGComputeContext _context;

        public float[] indicatorScores;
        public float[] matrix;
        public byte[] universe;
        public byte[] attributes;

        public byte[] ESGHeads;

        public byte[] DisjunctClustersHeads;

        public byte[] DisjunctClustersMap;

        public byte DisjunctClustersCount;

        private byte[] profilesWithResearch;

        public void Initialize(IESGComputeContext context)
        {
            if (initialized)
                throw new ArgumentException("already intialized");

            this._context = context;

            ESGHeads = new byte[] { };
            DisjunctClustersHeads = new byte[] { };
            DisjunctClustersMap = new byte[] { };
        }

        public ESGData(IESGComputeContext context, int companiesCount, int groupsHeadsCount)
        {
            this._context = context;
            this.companiesCount = companiesCount;
           

            this.indicatorScores = new float[indicatorsCount * companiesCount];
            this.matrix = new float[indicatorsCount * companiesCount];
            this.universe = new byte[companiesCount];
            this.attributes = new byte[companiesCount];
            this.groupsHeads = new int[groupsHeadsCount];
            this.profilesWithResearch = new byte[companiesCount];
        }

        public ESGData(IESGComputeContext context, int companiesCount, int[] groupsHeads)
        {
            this._context = context;
            this.companiesCount = companiesCount;


            this.indicatorScores = new float[indicatorsCount * companiesCount];
            this.matrix = new float[indicatorsCount * companiesCount];
            this.universe = new byte[companiesCount];
            this.attributes = new byte[companiesCount];
            this.groupsHeads = groupsHeads;
        }

        public void PushData()
        {
            if (ESGHeads == null)
            {
                LoadDefaultIndicatorsConfiguration();
            }

            _context.AddCompanySet(companiesCount, indicatorsCount, 4+DisjunctClustersCount, ESGHeads, DisjunctClustersHeads, DisjunctClustersCount, DisjunctClustersMap);
            _context.SetIndicators(1, companiesCount, indicatorsCount, indicatorScores);

            _context.AddMatrix(1, 1, companiesCount*indicatorsCount, matrix, companiesCount, profilesWithResearch);
            _context.AddUniverse(1, 1, companiesCount, universe);
            _context.SetAttributes(1, companiesCount, attributes);

        }

        public RatingsResult Ratings(byte filter, bool computeIndicatorsRatings,bool computeAggregatedWeights=false,bool returnWeights=false)
        {
            return _context.Ratings(1, 1, 1, filter,groupsHeads, computeIndicatorsRatings,computeAggregatedWeights,returnWeights);
        }

        public void SetProfileFlag(int companyIndex, byte value)
        {
            this.profilesWithResearch[companyIndex] = value;
        }
        public void SetRelativeWeight(int companyIndex, int indicatorIndex, float value)
        {
            this.matrix[companiesCount * indicatorIndex + companyIndex] = value;
        }

        public void SetScore(int companyIndex, int indicatorIndex, float value)
        {
            this.indicatorScores[companiesCount * indicatorIndex + companyIndex] = value;
        }

        public float GetIndicatorRating(int ci, int indicator)
        {
            var index = ci + companiesCount * indicator;
            return this.indicatorScores[index] * this.matrix[index];
        }

        public void SetIndicatorsConfiguration(byte[] esgHeads, byte[] disjunctClustersHeads, byte[] disjunctClustersMap)
        {
            this.ESGHeads = esgHeads;
            this.DisjunctClustersHeads = disjunctClustersHeads;
            this.DisjunctClustersMap = disjunctClustersMap;
        }

        public float GetWeight(int ci, int indicator)
        {
            var index = ci + companiesCount * indicator;
            return this.matrix[index];
        }

        public void LoadDefaultIndicatorsConfiguration()
        {


            var config = GetDefaultConfiguration();
            this.ESGHeads = config.ESGHeads;
            this.DisjunctClustersHeads = config.DisjunctClustersHeads;
            this.DisjunctClustersMap = config.DisjunctClustersMap;
            this.DisjunctClustersCount = config.DisjunctClustersCount;
        }


        public static IndicatorsConfiguration GetDefaultConfiguration()
        {
            IndicatorsConfiguration.IndicatorsConfigurationBuilder builder = new IndicatorsConfiguration.IndicatorsConfigurationBuilder("E", "S", "G");

            #region indicators
            builder.AddIndicator("E.1.1", "E", "P");
            builder.AddIndicator("E.1.1.1", "E", "D");
            builder.AddIndicator("E.1.10", "E", "QNP");
            builder.AddIndicator("E.1.11", "E", "QNP");
            builder.AddIndicator("E.1.12", "E", "QLP");
            builder.AddIndicator("E.1.2", "E", "P");
            builder.AddIndicator("E.1.2.1", "E", "P");
            builder.AddIndicator("E.1.2.2", "E", "P");
            builder.AddIndicator("E.1.2.3", "E", "P");
            builder.AddIndicator("E.1.2.4", "E", "D");
            builder.AddIndicator("E.1.2.6", "E", "QNP");
            builder.AddIndicator("E.1.2.7", "E", "QNP");
            builder.AddIndicator("E.1.2.8", "E", "QNP");
            builder.AddIndicator("E.1.3", "E", "P");
            builder.AddIndicator("E.1.3.2", "E", "P");
            builder.AddIndicator("E.1.3.3", "E", "P");
            builder.AddIndicator("E.1.3.4", "E", "P");
            builder.AddIndicator("E.1.3.5", "E", "P");
            builder.AddIndicator("E.1.4", "E", "QNP");
            builder.AddIndicator("E.1.5", "E", "D");
            builder.AddIndicator("E.1.6", "E", "D");
            builder.AddIndicator("E.1.7", "E", "P");
            builder.AddIndicator("E.1.7.1", "E", "P");
            builder.AddIndicator("E.1.7.2", "E", "P");
            builder.AddIndicator("E.1.8", "E", "P");
            builder.AddIndicator("E.1.9", "E", "QNP");
            builder.AddIndicator("E.2.1", "E", "P");
            builder.AddIndicator("E.2.1.1", "E", "P");
            builder.AddIndicator("E.2.1.10", "E", "P");
            builder.AddIndicator("E.2.1.2", "E", "P");
            builder.AddIndicator("E.2.1.3", "E", "P");
            builder.AddIndicator("E.2.1.4", "E", "P");
            builder.AddIndicator("E.2.1.5", "E", "P");
            builder.AddIndicator("E.2.1.6", "E", "P");
            builder.AddIndicator("E.2.1.7", "E", "QNP");
            builder.AddIndicator("E.2.1.8", "E", "QNP");
            builder.AddIndicator("E.2.1.9", "E", "P");
            builder.AddIndicator("E.2.2", "E", "QLP");
            builder.AddIndicator("E.3.1.1", "E", "QNP");
            builder.AddIndicator("E.3.1.10", "E", "P");
            builder.AddIndicator("E.3.1.11", "E", "QNP");
            builder.AddIndicator("E.3.1.12", "E", "P");
            builder.AddIndicator("E.3.1.13", "E", "P");
            builder.AddIndicator("E.3.1.14", "E", "QNP");
            builder.AddIndicator("E.3.1.15", "E", "QNP");
            builder.AddIndicator("E.3.1.16", "E", "QNP");
            builder.AddIndicator("E.3.1.17", "E", "QNP");
            builder.AddIndicator("E.3.1.2", "E", "QNP");
            builder.AddIndicator("E.3.1.3", "E", "QNP");
            builder.AddIndicator("E.3.1.4", "E", "QNP");
            builder.AddIndicator("E.3.1.5", "E", "QNP");
            builder.AddIndicator("E.3.1.6", "E", "P");
            builder.AddIndicator("E.3.1.7", "E", "P");
            builder.AddIndicator("E.3.1.8", "E", "QNP");
            builder.AddIndicator("E.3.1.9", "E", "P");
            builder.AddIndicator("E.3.2", "E", "QLP");
            builder.AddIndicator("G.1.1", "G", "P");
            builder.AddIndicator("G.1.1.1", "G", "P");
            builder.AddIndicator("G.1.2", "G", "P");
            builder.AddIndicator("G.1.3", "G", "P");
            builder.AddIndicator("G.1.3.1", "G", "P");
            builder.AddIndicator("G.1.3.2", "G", "P");
            builder.AddIndicator("G.1.3.3", "G", "P");
            builder.AddIndicator("G.1.3.4", "G", "P");
            builder.AddIndicator("G.1.3.5", "G", "P");
            builder.AddIndicator("G.1.4", "G", "D");
            builder.AddIndicator("G.1.4.1", "G", "P");
            builder.AddIndicator("G.1.4.3", "G", "P");
            builder.AddIndicator("G.1.4.4", "G", "P");
            builder.AddIndicator("G.1.4.5", "G", "P");
            builder.AddIndicator("G.1.4.6", "G", "P");
            builder.AddIndicator("G.1.5", "G", "QLP");
            builder.AddIndicator("G.2.1", "G", "D");
            builder.AddIndicator("G.2.10", "G", "P");
            builder.AddIndicator("G.2.11", "G", "P");
            builder.AddIndicator("G.2.12", "G", "P");
            builder.AddIndicator("G.2.13", "G", "QLP");
            builder.AddIndicator("G.2.2", "G", "D");
            builder.AddIndicator("G.2.3", "G", "D");
            builder.AddIndicator("G.2.4", "G", "D");
            builder.AddIndicator("G.2.5", "G", "P");
            builder.AddIndicator("G.2.5.1", "G", "P");
            builder.AddIndicator("G.2.6", "G", "P");
            builder.AddIndicator("G.2.7", "G", "P");
            builder.AddIndicator("G.2.8", "G", "P");
            builder.AddIndicator("G.2.9", "G", "P");
            builder.AddIndicator("G.3.1", "G", "P");
            builder.AddIndicator("G.3.2", "G", "P");
            builder.AddIndicator("G.3.3.1", "G", "D");
            builder.AddIndicator("G.3.4", "G", "QLP");
            builder.AddIndicator("S.1.1", "S", "P");
            builder.AddIndicator("S.1.1.1", "S", "P");
            builder.AddIndicator("S.1.2", "S", "P");
            builder.AddIndicator("S.1.3", "S", "P");
            builder.AddIndicator("S.1.4", "S", "QNP");
            builder.AddIndicator("S.1.5", "S", "QNP");
            builder.AddIndicator("S.1.5.1", "S", "QNP");
            builder.AddIndicator("S.1.6", "S", "QNP");
            builder.AddIndicator("S.1.6.1", "S", "QNP");
            builder.AddIndicator("S.1.6.2", "S", "P");
            builder.AddIndicator("S.1.6.3", "S", "P");
            builder.AddIndicator("S.1.6.4", "S", "P");
            builder.AddIndicator("S.1.6.5", "S", "QNP");
            builder.AddIndicator("S.1.6.6", "S", "QNP");
            builder.AddIndicator("S.1.7", "S", "QLP");
            builder.AddIndicator("S.2.1", "S", "P");
            builder.AddIndicator("S.2.1.1", "S", "P");
            builder.AddIndicator("S.2.1.2", "S", "P");
            builder.AddIndicator("S.2.1.3", "S", "P");
            builder.AddIndicator("S.2.1.3.1", "S", "P");
            builder.AddIndicator("S.2.2", "S", "P");
            builder.AddIndicator("S.2.2.1", "S", "P");
            builder.AddIndicator("S.2.2.2", "S", "D");
            builder.AddIndicator("S.2.2.2.1", "S", "P");
            builder.AddIndicator("S.2.2.3", "S", "P");
            builder.AddIndicator("S.2.2.4", "S", "QNP");
            builder.AddIndicator("S.2.3", "S", "QLP");
            builder.AddIndicator("S.3.1.1", "S", "P");
            builder.AddIndicator("S.3.1.10", "S", "P");
            builder.AddIndicator("S.3.1.11", "S", "P");
            builder.AddIndicator("S.3.1.12", "S", "P");
            builder.AddIndicator("S.3.1.2", "S", "P");
            builder.AddIndicator("S.3.1.3", "S", "P");
            builder.AddIndicator("S.3.1.4", "S", "P");
            builder.AddIndicator("S.3.1.5", "S", "P");
            builder.AddIndicator("S.3.1.6", "S", "P");
            builder.AddIndicator("S.3.1.7", "S", "P");
            builder.AddIndicator("S.3.1.8", "S", "QNP");
            builder.AddIndicator("S.3.1.9", "S", "P");
            builder.AddIndicator("S.3.2.1", "S", "P");
            builder.AddIndicator("S.3.3", "S", "QLP");
            builder.AddIndicator("S.4.1", "S", "QNP");
            builder.AddIndicator("S.4.2.1", "S", "P");
            builder.AddIndicator("S.4.2.10", "S", "P");
            builder.AddIndicator("S.4.2.11", "S", "P");
            builder.AddIndicator("S.4.2.12", "S", "P");
            builder.AddIndicator("S.4.2.13", "S", "P");
            builder.AddIndicator("S.4.2.14", "S", "QNP");
            builder.AddIndicator("S.4.2.2", "S", "P");
            builder.AddIndicator("S.4.2.3", "S", "P");
            builder.AddIndicator("S.4.2.4", "S", "P");
            builder.AddIndicator("S.4.2.5", "S", "P");
            builder.AddIndicator("S.4.2.6", "S", "P");
            builder.AddIndicator("S.4.2.7", "S", "P");
            builder.AddIndicator("S.4.2.8", "S", "P");
            builder.AddIndicator("S.4.2.9", "S", "P");
            builder.AddIndicator("S.4.3", "S", "QLP");
            builder.AddIndicator("S.5.1", "S", "P");
            builder.AddIndicator("S.5.2", "S", "P");
            builder.AddIndicator("S.5.3", "S", "QNP");

            #endregion

            return builder.Build();

        }

       
    }


    public class ESGCpuComputation {

        public List<GroupResult<int>> CpuRatings(ESGData data)
        {

            this._clusterStarts = data.DisjunctClustersHeads;
            this._clusterGroups = data.DisjunctClustersMap;

            var esgGroup = Enumerable.Range(0, data.indicatorsCount)
                .GroupBy(a => GetESGGroup(a));

            var clusterGroup = Enumerable.Range(0, data.indicatorsCount)
                .GroupBy(a => GetClusterGroup(a));

            var esgResult = Enumerable.Range(0, data.companiesCount)
                .SelectMany((ci) =>
                    esgGroup.Select(grp =>
                            new
                            {
                                Cluster = grp.Key,
                                Company = ci,
                                Score = ConditionalDiv(
                                    grp.Aggregate((float)0, (accumelated, i) => accumelated + data.GetIndicatorRating(ci, i)),
                                    grp.Aggregate((float)0, (accumelated, i) => accumelated + data.GetWeight(ci, i))
                                    ),
                            })
                ).GroupBy(r => r.Cluster)
                .Select(grp =>
                new GroupResult<int>
                {
                    Key = grp.Key,
                    Results = grp.Aggregate(new Dictionary<int, float>(), (accumulator, a) => { accumulator.Add(a.Company, a.Score); return accumulator; })
                }
                )
                .OrderBy(x => x.Key).ToList();



            var clustersResult = Enumerable.Range(0, data.companiesCount)
                .SelectMany((ci) =>
                    clusterGroup.Select(grp =>
                            new
                            {
                                Company = ci,
                                Cluster = grp.Key,
                                Score = grp.Aggregate((float)0, (accumelated, i) => accumelated + data.GetIndicatorRating(ci, i)),
                            })
                )
                .GroupBy(r => r.Cluster)
                .Select(grp =>
                new GroupResult<int>
                {
                    Key = grp.Key,
                    Results = grp.Aggregate(new Dictionary<int, float>(), (accumulator, a) => { accumulator.Add(a.Company, a.Score); return accumulator; })
                }
                )
                 .OrderBy(x => x.Key).ToList();



            var companyRatingResult =
                Enumerable.Range(0, data.companiesCount)
                .Select((ci) =>
                    new
                    {
                        Cluster = 0,
                        Company = ci,
                        Score = Enumerable.Range(0, data.indicatorsCount).Aggregate((float)0, (accum, i) => accum + data.GetIndicatorRating(ci, i))
                    })
                .GroupBy(r => r.Cluster)
                .Select(grp =>
                new GroupResult<int>
                {
                    Key = grp.Key,
                    Results = grp.Aggregate(new Dictionary<int, float>(), (accumulator, a) => { accumulator.Add(a.Company, a.Score); return accumulator; })
                }

                )
                 .OrderBy(x => x.Key).ToList();


            companyRatingResult.AddRange(esgResult);
            companyRatingResult.AddRange(clustersResult);

            return companyRatingResult;
        }

        public int GetESGGroup(int index)
        {
            byte[] esgEnds = new byte[] { 56, 90 };
            for (int i = 0; i < esgEnds.Length; i++)
            {
                if (index < esgEnds[i])
                    return i;
            }

            return esgEnds.Length;
        }


        private float ConditionalDiv(float p1, float p2)
        {
            if (p2 == 0)
                return float.NaN;
            return p1 / p2;
        }


        byte[] _clusterStarts = null;//new byte[8] { 29, 32, 52, 62, 87, 94, 95, 138 }; // clusters interval (8 + 1 intervals)
        byte[] _clusterGroups = null;//new byte[9] { 1, 2, 3, 0, 1, 2, 0, 1, 3 }; // group for each interval 0 - num groups <= intervals-1

        public int GetClusterGroup(int index)
        {
            for (int i = 0; i < _clusterStarts.Length; i++)
            {
                if (index < _clusterStarts[i])
                    return _clusterGroups[i];
            }

            return _clusterGroups[_clusterStarts.Length];
        }


        public ESGData GetRandomESGDataWitoutContext(int companiesCount)
        {
            var data = new ESGData(null, companiesCount, 0);

            float[] referenceData = new float[data.companiesCount];
            for (int i = 0; i < data.companiesCount; i++)
            {
                data.universe[i] = 1;
                data.attributes[i] = 1;
            }

            int seed = (int)(DateTime.Now.Ticks % 10e+10);
            Random random = new Random(seed);
            for (int i = 0; i < data.companiesCount; i++)
            {
                for (int k = 0; k < data.indicatorsCount; k++)
                {
                    data.SetScore(i, k, random.Next(1, 100));
                    data.SetRelativeWeight(i, k, (float)random.NextDouble());
                }
            }
            
            return data;
        }

        
    }

    public class GroupResult<T>
    {
        public T Key;
        public Dictionary<int, float> Results;
    }
}
